#region Header File
//
// Edge.cs - Graph's Edge
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

#region Using Statements
using System;
using System.Diagnostics;
using System.Collections.Generic;
#endregion

namespace Dff
{
    /// <summary>
    /// Finite Queue (Bounded Buffer) that conects an output (O) port to and input (I) port
    /// </summary>
    /// <typeparam name="T">The type of the tokens transfered</typeparam>
    public class Edge<T> 
    {
        // buffer and management for local queue. This is the unique local queue, each port has its
        // own view of this queue, for example: an I port can Dequeue() token and view a tokenCount 
        // lower than another I port that had not donde Dequeue() invokation
        int qlen = 0;
        int head = 0;
        int tail = 0;
        
        T[] qbuf;
        object syncRoot = new object();

        Port<T> iport;  // connected I port
        Port<T> oport;  // connected O port        
   
        #region Constructor
        public Edge(int capacity)
        {
            this.qbuf = new T[capacity];
            Clear();
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Adds one port to the to the collection of ports connected
        /// </summary>
        /// <param name="port">The new port</param>
        public void AddPort(Port<T> port)
        {
            if (port.Type == PortType.I)
            {
                iport = port;
            }
            else
            {
                oport = port;
            }

            // Raise PortAttach event
            port.OnPortAttach(this);
        }

        /// <summary>
        /// Removes one port to the collection of ports connected
        /// </summary>
        /// <param name="port">Port to be deleted</param>
        public void DelPort(Port<T> port)
        {
            Debug.Assert((port == iport) || (port == oport));

            /**/if (port == iport)
            {
                iport = null;
                port.OnPortDetach();
            }
            else if (port == oport)
            {
                oport = null;
                port.OnPortDetach();
            }
        }

        /// <summary>
        /// Enqueues one token
        /// </summary>
        /// <param name="src">Token to be enqueued</param>
        /// <returns>The number of tokens actually enqueued, 1 or 0 if no free space</returns>
        public int Enqueue(T token)
        {
            int bakcount;

            lock (syncRoot)
            {
                if (qlen == qbuf.Length)
                    return 0;

                qbuf[tail] = token;
                tail = (tail + 1) % qbuf.Length;
                qlen++;
                bakcount = qlen;
            }

            // only if i-port is connected
            if (iport != null)
            {
                iport.OnEdgeChangeForIPort(bakcount);
            }

            return 1;
        }

        /// <summary>
        /// Enqueues count tokens in the edge read from buffer src. If the queue has not enought 
        /// free space, the method returns the actual amount of tokens enqueued
        /// </summary>
        /// <param name="buffer">Array that contains the tokens to enqueue</param>
        /// <param name="offset">Offset in the array</param>
        /// <param name="count">Number of tokens to enqueue</param>
        /// <returns>Number of tokens actually enqueued</returns>
        public int Enqueue(T[] src, int offset, int count)
        {
            int actual;
            int bakcount;

            lock (syncRoot)
            {
                actual = (count > qbuf.Length - qlen) ? count : qbuf.Length - qlen;

                for (int i = 0; i < actual; i++)
                {
                    qbuf[tail] = src[offset + i];
                    tail = (tail + 1) % qbuf.Length;
                }

                qlen += actual;
                bakcount = qlen;
            }

            // only if i-port is connected
            if (iport != null)
            {
                iport.OnEdgeChangeForIPort(bakcount);
            }

            return actual;
        }

        /// <summary>
        /// Dequeues one token
        /// </summary>
        /// <param name="dst">Token dequeued</param>
        /// <returns>The number of tokens actually enqueued, 1 or 0 if no data</returns>
        public int Dequeue(out T dst)
        {
            dst = default(T);
            int baklen;

            lock (syncRoot)
            {
                if (qlen < 1)
                    return 0;

                dst  = qbuf[head];
                head = (head + 1) % qbuf.Length;
                qlen--;
                baklen = qlen;
            }

            // only if o-port is connected
            if (oport != null)
            {
                oport.OnEdgeChangeForIPort(baklen);
            }

            return 1;
        }

        /// <summary>
        /// Dequeues count tokens and stores them in the buffer dst. If the queue has not enought 
        /// data returns the actual amount of tokens dequeued
        /// </summary>
        /// <param name="buffer">Array where to store the dequeued tokens</param>
        /// <param name="offset">Offset in the array</param>
        /// <param name="count">Number of tokens to dequeue</param>
        /// <returns>Number of tokens actually dequeued</returns>
        public int Dequeue(T[] dst, int offset, int count)
        {
            int actual;
            int baklen;

            lock (syncRoot)
            {
                actual = (qlen < count) ? qlen : count;

                for (int i = 0; i < actual; i++)
                {
                    dst[offset + i] = qbuf[head];
                    head            = (head + 1) % qbuf.Length;
                }

                qlen  -= actual;
                baklen = qlen;
            }

            // only if o-port is connected
            if (oport != null)
            {
                oport.OnEdgeChangeForOPort(baklen);
            }

            return actual;
        }


        /// <summary>
        /// Removes all objects from the RingBuffer<![CDATA[<T>]]>
        /// </summary>
        public void Clear()
        {
            this.head = 0;
            this.tail = 0;
            this.qlen = 0;
        }
        #endregion


        #region Properties
        /// <summary>
        /// FIFO capacity in slots. Each slot stores one token
        /// </summary>
        /// <returns>Slot count</returns>
        public int Capacity
        {
            get
            {
                return qbuf.Length;
            }
        }

        /// <summary>
        /// FIFO free slots count 
        /// </summary>
        /// <returns>Slot count</returns>
        public int Space
        {
            get
            {
                int r;
                lock (syncRoot)
                {
                    r = qbuf.Length - qlen;
                }
                return r;
            }
        }

        /// <summary>
        /// FIFO used slots count
        /// </summary>
        /// <returns>Slot count</returns>
        public int Count
        {
            get
            {
                int r;
                lock (syncRoot)
                {
                    r = qlen;
                }
                return r;
            }
        }
        #endregion
   
   }
   
}